add keep_events_for as an optional field for agents to decide how long to store events

Andrew Cantino 10 years ago
parent
commit
229e24ea80

+ 1 - 0
CHANGES.md

@@ -1,5 +1,6 @@
1 1
 # Changes
2 2
 
3
+* 0.3 (Dec 22, 2013) - Agents now have an optional keep_events_for option that is propagated to created events' expires_at field for eventual cleanup.
3 4
 * 0.2 (Nov 6, 2013)    - PeakDetectorAgent now uses `window_duration_in_days` and `min_peak_spacing_in_days`.  Additionally, peaks trigger when the time series rises over the standard deviation multiple, not after it starts to fall.
4 5
 * June 29, 2013        - Removed rails\_admin because it was causing deployment issues. Better to have people install their favorite admin tool if they want one.
5 6
 * June, 2013           - A number of new agents have been contributed, including interfaces to Weibo, Twitter, and Twilio, as well as Agents for translation, sentiment analysis, and for posting and receiving webhooks.

+ 17 - 0
app/assets/javascripts/application.js.coffee.erb

@@ -34,6 +34,12 @@ showLinks = ->
34 34
   $(".link-region .cannot-receive-events").hide()
35 35
   showEventDescriptions()
36 36
 
37
+hideEventCreation = ->
38
+  $(".event-related-region").hide()
39
+
40
+showEventCreation = ->
41
+  $(".event-related-region").show()
42
+
37 43
 showEventDescriptions = ->
38 44
   if $("#agent_source_ids").val()
39 45
     $.getJSON "/agents/event_descriptions", { ids: $("#agent_source_ids").val().join(",") }, (json) =>
@@ -131,6 +137,11 @@ $(document).ready ->
131 137
         else
132 138
           hideLinks()
133 139
 
140
+        if json.can_create_events
141
+          showEventCreation()
142
+        else
143
+          hideEventCreation()
144
+
134 145
         $(".description").html(json.description_html) if json.description_html?
135 146
 
136 147
         if $("#agent_options").hasClass("showing-default") || $("#agent_options").val().match(/\A\s*(\{\s*\}|)\s*\Z/g)
@@ -152,3 +163,9 @@ $(document).ready ->
152 163
       showLinks()
153 164
     else
154 165
       hideLinks()
166
+
167
+  if $(".event-related-region")
168
+    if $(".event-related-region").data("can-create-events") == true
169
+      showEventCreation()
170
+    else
171
+      hideEventCreation()

+ 1 - 0
app/controllers/agents_controller.rb

@@ -33,6 +33,7 @@ class AgentsController < ApplicationController
33 33
     render :json => {
34 34
         :can_be_scheduled => agent.can_be_scheduled?,
35 35
         :can_receive_events => agent.can_receive_events?,
36
+        :can_create_events => agent.can_create_events?,
36 37
         :options => agent.default_options,
37 38
         :description_html => agent.html_description
38 39
     }

+ 32 - 24
app/models/agent.rb

@@ -16,7 +16,9 @@ class Agent < ActiveRecord::Base
16 16
   SCHEDULES = %w[every_2m every_5m every_10m every_30m every_1h every_2h every_5h every_12h every_1d every_2d every_7d
17 17
                  midnight 1am 2am 3am 4am 5am 6am 7am 8am 9am 10am 11am noon 1pm 2pm 3pm 4pm 5pm 6pm 7pm 8pm 9pm 10pm 11pm]
18 18
 
19
-  attr_accessible :options, :memory, :name, :type, :schedule, :source_ids
19
+  EVENT_RETENTION_SCHEDULES = [["Forever", 0], ["1 day", 1], *([2, 3, 4, 5, 7, 14, 21, 30, 45, 90, 180, 365].map {|n| ["#{n} days", n] })]
20
+
21
+  attr_accessible :options, :memory, :name, :type, :schedule, :source_ids, :keep_events_for
20 22
 
21 23
   validates_presence_of :name, :user
22 24
   validate :sources_are_owned
@@ -83,22 +85,16 @@ class Agent < ActiveRecord::Base
83 85
     most_recent_log.try(:level) == 4
84 86
   end
85 87
 
86
-  def sources_are_owned
87
-    errors.add(:sources, "must be owned by you") unless sources.all? {|s| s.user == user }
88
-  end
89
-
90 88
   def create_event(attrs)
91 89
     if can_create_events?
92
-      events.create!({ :user => user }.merge(attrs))
90
+      events.create!({ :user => user, :expires_at => new_event_expiration_date }.merge(attrs))
93 91
     else
94 92
       error "This Agent cannot create events!"
95 93
     end
96 94
   end
97 95
 
98
-  def validate_schedule
99
-    unless cannot_be_scheduled?
100
-      errors.add(:schedule, "is not a valid schedule") unless SCHEDULES.include?(schedule.to_s)
101
-    end
96
+  def new_event_expiration_date
97
+    keep_events_for > 0 ? keep_events_for.days.from_now : nil
102 98
   end
103 99
 
104 100
   def make_message(payload, message = options[:message])
@@ -112,14 +108,6 @@ class Agent < ActiveRecord::Base
112 108
     end
113 109
   end
114 110
 
115
-  def set_default_schedule
116
-    self.schedule = default_schedule unless schedule.present? || cannot_be_scheduled?
117
-  end
118
-
119
-  def unschedule_if_cannot_schedule
120
-    self.schedule = nil if cannot_be_scheduled?
121
-  end
122
-
123 111
   def last_event_at
124 112
     @memoized_last_event_at ||= most_recent_event.try(:created_at)
125 113
   end
@@ -152,12 +140,6 @@ class Agent < ActiveRecord::Base
152 140
     !cannot_create_events?
153 141
   end
154 142
 
155
-  def set_last_checked_event_id
156
-    if newest_event_id = Event.order("id desc").limit(1).pluck(:id).first
157
-      self.last_checked_event_id = newest_event_id
158
-    end
159
-  end
160
-
161 143
   def log(message, options = {})
162 144
     puts "Agent##{id}: #{message}" unless Rails.env.test?
163 145
     AgentLog.log_for_agent(self, message, options)
@@ -167,6 +149,32 @@ class Agent < ActiveRecord::Base
167 149
     log(message, options.merge(:level => 4))
168 150
   end
169 151
 
152
+  # Validations and Callbacks
153
+
154
+  def sources_are_owned
155
+    errors.add(:sources, "must be owned by you") unless sources.all? {|s| s.user == user }
156
+  end
157
+
158
+  def validate_schedule
159
+    unless cannot_be_scheduled?
160
+      errors.add(:schedule, "is not a valid schedule") unless SCHEDULES.include?(schedule.to_s)
161
+    end
162
+  end
163
+
164
+  def set_default_schedule
165
+    self.schedule = default_schedule unless schedule.present? || cannot_be_scheduled?
166
+  end
167
+
168
+  def unschedule_if_cannot_schedule
169
+    self.schedule = nil if cannot_be_scheduled?
170
+  end
171
+
172
+  def set_last_checked_event_id
173
+    if newest_event_id = Event.order("id desc").limit(1).pluck(:id).first
174
+      self.last_checked_event_id = newest_event_id
175
+    end
176
+  end
177
+
170 178
   # Class Methods
171 179
   class << self
172 180
     def cannot_be_scheduled!

+ 10 - 0
app/views/agents/_form.html.erb

@@ -44,6 +44,16 @@
44 44
     </div>
45 45
   </div>
46 46
 
47
+
48
+  <div class='event-related-region' data-can-create-events="<%= @agent.can_create_events? %>">
49
+    <div class="control-group">
50
+      <%= f.label :keep_events_for, "Keep events", :class => 'control-label' %>
51
+      <div class="controls">
52
+        <%= f.select :keep_events_for, options_for_select(Agent::EVENT_RETENTION_SCHEDULES, @agent.keep_events_for), {}, :class => 'span4' %>
53
+      </div>
54
+    </div>
55
+  </div>
56
+
47 57
   <div class="control-group">
48 58
     <%= f.label :sources, :class => 'control-label' %>
49 59
     <div class="controls link-region" data-can-receive-events="<%= @agent.can_receive_events? %>">

+ 5 - 0
app/views/agents/show.html.erb

@@ -84,6 +84,11 @@
84 84
 
85 85
             <% if @agent.can_create_events? %>
86 86
               <p>
87
+                <b>Keep events:</b>
88
+                <%= (Agent::EVENT_RETENTION_SCHEDULES.detect {|s| s.last == @agent.keep_events_for } || [@agent.keep_events_for]).first %>
89
+              </p>
90
+
91
+              <p>
87 92
                 <b>Last event created:</b>
88 93
                 <%= @agent.last_event_at ? time_ago_in_words(@agent.last_event_at) + " ago" : "never" %>
89 94
               </p>

+ 5 - 0
app/views/events/show.html.erb

@@ -6,6 +6,11 @@
6 6
       </div>
7 7
 
8 8
       <p>
9
+        <b>Expires in:</b>
10
+        <%= @event.expires_at ? time_ago_in_words(@event.expires_at) : 'never' %>
11
+      </p>
12
+
13
+      <p>
9 14
         <b>Payload:</b>
10 15
         <pre><%= Utils.pretty_jsonify @event.payload || {} %></pre>
11 16
       </p>

+ 3 - 2
app/views/home/_signed_out_index.html.erb

@@ -2,9 +2,10 @@
2 2
   <div class='row'>
3 3
     <div class="span5 offset2">
4 4
       <h1>Your agents are standing by</h1>
5
-      <p>Know the world around you</p>
5
+      <p>Huginn monitors the world and acts on your behalf.</p>
6 6
 
7
-      <%= link_to "Signup", new_user_registration_path, :class => "btn btn-primary btn-large center" %>
7
+      <%= link_to "Login", new_user_session_path, :class => "btn btn-large" %>
8
+      <%= link_to "Signup", new_user_registration_path, :class => "btn btn-primary btn-large" %>
8 9
     </div>
9 10
     <div class="span3">
10 11
       <%= image_tag 'odin.jpg', :class => 'img-rounded', :title => "Wägner, Wilhelm. 1882. Nordisch-germanische Götter und Helden. Otto Spamer, Leipzig & Berlin. Page 7." %>

+ 5 - 0
db/migrate/20131222211558_add_keep_events_for_to_agents.rb

@@ -0,0 +1,5 @@
1
+class AddKeepEventsForToAgents < ActiveRecord::Migration
2
+  def change
3
+    add_column :agents, :keep_events_for, :integer, :null => false, :default => 0
4
+  end
5
+end

+ 4 - 3
db/schema.rb

@@ -11,7 +11,7 @@
11 11
 #
12 12
 # It's strongly recommended to check this file into your version control system.
13 13
 
14
-ActiveRecord::Schema.define(:version => 20131105063248) do
14
+ActiveRecord::Schema.define(:version => 20131222211558) do
15 15
 
16 16
   create_table "agent_logs", :force => true do |t|
17 17
     t.integer  "agent_id",                         :null => false
@@ -33,10 +33,11 @@ ActiveRecord::Schema.define(:version => 20131105063248) do
33 33
     t.datetime "last_check_at"
34 34
     t.datetime "last_receive_at"
35 35
     t.integer  "last_checked_event_id"
36
-    t.datetime "created_at",                                  :null => false
37
-    t.datetime "updated_at",                                  :null => false
36
+    t.datetime "created_at",                                                 :null => false
37
+    t.datetime "updated_at",                                                 :null => false
38 38
     t.text     "memory",                :limit => 2147483647
39 39
     t.datetime "last_webhook_at"
40
+    t.integer  "keep_events_for",                             :default => 0, :null => false
40 41
   end
41 42
 
42 43
   add_index "agents", ["schedule"], :name => "index_agents_on_schedule"

+ 2 - 0
spec/fixtures/agents.yml

@@ -33,6 +33,7 @@ bob_weather_agent:
33 33
   user: bob
34 34
   schedule: "midnight"
35 35
   name: "SF Weather"
36
+  keep_events_for: 45
36 37
   options: <%= {
37 38
                  :location => 94102,
38 39
                  :lat => 37.779329,
@@ -45,6 +46,7 @@ jane_weather_agent:
45 46
   user: jane
46 47
   schedule: "midnight"
47 48
   name: "SF Weather"
49
+  keep_events_for: 30
48 50
   options: <%= {
49 51
                  :location => 94103,
50 52
                  :lat => 37.779329,

+ 24 - 0
spec/models/agent_spec.rb

@@ -303,4 +303,28 @@ describe Agent do
303 303
       end
304 304
     end
305 305
   end
306
+
307
+  describe "#create_event" do
308
+    describe "when the agent has keep_events_for set" do
309
+      before do
310
+        agents(:jane_weather_agent).keep_events_for.should > 0
311
+      end
312
+
313
+      it "sets expires_at on created events" do
314
+        event = agents(:jane_weather_agent).create_event :payload => "hi!"
315
+        event.expires_at.should be_within(5).of(agents(:jane_weather_agent).keep_events_for.days.from_now)
316
+      end
317
+    end
318
+
319
+    describe "when the agent does not have keep_events_for set" do
320
+      before do
321
+        agents(:jane_website_agent).keep_events_for.should == 0
322
+      end
323
+
324
+      it "does not set expires_at on created events" do
325
+        event = agents(:jane_website_agent).create_event :payload => "hi!"
326
+        event.expires_at.should be_nil
327
+      end
328
+    end
329
+  end
306 330
 end